//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-12-03     ZhangYihua   first version
//
// Description  : 
//################################################################################

module sym_coe_fir #(
parameter           CDW                     = 16,   // coe data width
parameter           CFW                     = 15,   // coe fraction width
parameter           IDW                     = 12,   // input data width
parameter           IFW                     = 9,    // input fraction width
parameter           ODW                     = 12,   // output data width
parameter           OFW                     = 9,    // output fraction width
parameter           TAP_NUM                 = 15,   // must TAP_NUM>=2

// the following parameters are calculated automatically
parameter           COE_NUM                 = TAP_NUM/2 + TAP_NUM%2,

// the following parameters specified by user
parameter           CSCD_LEN_MAX            = 4,    // CSCD_LEN_MAX>=2, cascade length, if too bigger, bad Fmax
parameter [COE_NUM-1:0]     COE_ZERO_MAP    = {COE_NUM{1'b0}},  // for saving area, each bit for a COE

// the following parameters are calculated automatically
parameter           COE_ZERO_NUM            = bit_sum_f(COE_ZERO_MAP),
parameter           PRDCT_NUM               = COE_NUM-COE_ZERO_NUM,
parameter           CSCD_GRP                = PRDCT_NUM/CSCD_LEN_MAX + (((PRDCT_NUM%CSCD_LEN_MAX)==0) ? 0 : 1),
parameter           CSCD_LEN                = PRDCT_NUM/CSCD_GRP + (((PRDCT_NUM%CSCD_GRP)==0) ? 0 : 1),
parameter           LVL_NUM                 = (CSCD_GRP<=1) ? 1 : $clog2(CSCD_GRP),

// the following parameters specified by user
parameter [LVL_NUM-1:0]     LVL_REG         = {LVL_NUM{1'b0}}   // specify register for each level
) ( 
input                                       rst_n,
input                                       clk,

input                                       in_vld,
input               [COE_NUM*CDW-1:0]       in_coe,     // {CM-1, .... C1, C0}, M=COE_NUM
input       signed  [IDW-1:0]               in_dat,

output                                      out_vld,
output      signed  [ODW-1:0]               out_dat 
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          PRDCT_DW                = IDW+1+CDW;
localparam          CSCD_DW                 = PRDCT_DW+$clog2(CSCD_LEN);
localparam          SUM_DW                  = CSCD_DW+$clog2(CSCD_GRP);
localparam [PRDCT_NUM-1:0]  CSCD_HEAD_MAP   = alloc_head_f(PRDCT_NUM, CSCD_GRP);
localparam [PRDCT_NUM-1:0]  CSCD_TAIL_MAP   = (CSCD_HEAD_MAP>>1) | (1'b1<<(PRDCT_NUM-1));

reg         signed  [IDW-1:0]               pipe_dat[TAP_NUM-1:0];
reg                                         pipe_vld;
reg         signed  [CDW-1:0]               coe_ary[PRDCT_NUM-1:0];
reg         signed  [IDW-1:0]               dat_ary[PRDCT_NUM-1:0];
reg         signed  [IDW-1:0]               dat2_ary[PRDCT_NUM-1:0];
wire        signed  [CSCD_DW-1:0]           cscd_ary[PRDCT_NUM:0];
reg                                         prdct_vld;
reg                                         cscd_vld;
reg                 [CSCD_GRP*CSCD_DW-1:0]  cscd_tail;
wire        signed  [SUM_DW-1:0]            sum;

//################################################################################
// main
//################################################################################

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        pipe_vld <=`U_DLY 1'b0;
    end else begin
        pipe_vld <=`U_DLY in_vld;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        pipe_dat[0] <=`U_DLY {IDW{1'b0}};
    end else if (in_vld==1'b1) begin
        pipe_dat[0] <=`U_DLY in_dat;
    end
end

genvar g0;
generate for (g0=1; g0<TAP_NUM; g0=g0+1) begin:G_PIPE
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            pipe_dat[g0] <=`U_DLY {IDW{1'b0}};
        end else if (pipe_vld==1'b1) begin
            pipe_dat[g0] <=`U_DLY pipe_dat[g0-1];
        end else
            ;
    end
end endgenerate

always@(*) begin:PICK_NZ
    integer         s;
    integer         i;
    
    s = 0;
    for (i=0; i<COE_NUM; i=i+1) begin
        if (COE_ZERO_MAP[i]==1'b0) begin
            coe_ary[s] = in_coe[i*CDW+:CDW];
            dat_ary[s] = pipe_dat[i];
            dat2_ary[s] = pipe_dat[TAP_NUM-1-i];
            s = s + 1;
        end
    end
end

assign cscd_ary[0] = {CSCD_DW{1'b0}};

genvar g1;
generate for (g1=0; g1<PRDCT_NUM; g1=g1+1) begin:G_ADD_MUL
    // out_cscd = in_coe*(in_dat + in_dat_2) + in_cscd;
    mul_add_cscd_wrap #(
        .CDW                            (CDW                            ),	// CDW<=19
        .IDW                            (IDW                            ),	// IDW<=18
        .VLD_USE                        (1'b1                           ),
        .PRE_ADD_EN                     ((TAP_NUM%2==1 && g1==(PRDCT_NUM-1)) ? 1'b0 : 1'b1),
        .IN_REG_EN                      (1'b0                           ),
        .P_REG_EN                       (1'b1                           ),
        .CSCD_REG_EN                    (CSCD_TAIL_MAP[g1]              ),
        .CSCD_DW                        (CSCD_DW                        ),
        .CSCD_HEAD                      (CSCD_HEAD_MAP[g1]              ),
        .CSCD_TAIL                      (CSCD_TAIL_MAP[g1]              )
    ) u_mul_add_cscd (
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .in_vld                         (pipe_vld                       ),
        .in_coe                         (coe_ary[g1]                    ),
        .in_dat                         (dat_ary[g1]                    ),
        .in_dat_2                       (dat2_ary[g1]                   ),

        .in_cscd                        (cscd_ary[g1]                   ),
        .out_cscd                       (cscd_ary[g1+1]                 )
    );
end endgenerate

always@(*) begin:PICK_TAIL
    integer         s;
    integer         i;
    
    s = 0;
    for (i=0; i<PRDCT_NUM; i=i+1) begin
        if (CSCD_TAIL_MAP[i]==1'b1) begin
            cscd_tail[s*CSCD_DW+:CSCD_DW] = cscd_ary[i+1];
            s = s + 1;
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        prdct_vld <=`U_DLY 1'b0;
        cscd_vld  <=`U_DLY 1'b0;
    end else begin
        prdct_vld <=`U_DLY pipe_vld;
        cscd_vld  <=`U_DLY prdct_vld;
    end
end

add_all #(
        .OPRD_BW                        (CSCD_DW                        ),	// bit width of single operand
        .OPRD_NUM                       (CSCD_GRP                       ),	// number of operands, must 2<=OPRD_NUM<=16
        .OPRD_SIGNED                    (1'b1                           ),	// 1'b0: unsigned; 1'b1: signed;
        .LVL_NUM                        (LVL_NUM                        ),
        .LVL_REG                        (LVL_REG                        )
) u_add_all ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (1'b1                           ),

        .vld_in                         (cscd_vld                       ),
        .oprds                          (cscd_tail                      ),

        .vld_out                        (out_vld                        ),
        .sum_o                          (sum                            )
);

s_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (SUM_DW                         ),	// input data width
        .IFW                            (IFW+CFW                        ),	// input fractional width
        .ODW                            (ODW                            ),	// output data width
        .OFW                            (OFW                            )	// output fractional width
) u_out_dat ( 
        .id                             (sum                            ),	// s(IDW, IFW), the MSB is sign
        .od                             (out_dat                        ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
);

function integer bit_sum_f;
    input [COE_NUM-1:0]     vect;

    integer t;
    integer i;
    begin
        t = 0;
        for (i=0; i<COE_NUM; i=i+1) begin
            t = t + vect[i];
        end

        bit_sum_f = t;
    end
endfunction

function [PRDCT_NUM-1:0]  alloc_head_f;
    input integer n;
    input integer g;

    integer d, l;
    integer i, j, k;
    begin
        d = n/g;
        l = n%g;

        alloc_head_f = {PRDCT_NUM{1'b0}};
        alloc_head_f[0] = 1'b1;
        j = 0;
        k = (j<l) ? d+1 : d;
        for(i=0; i<n; i=i+1) begin
            if (i==k) begin
                alloc_head_f[i] = 1'b1;
                j = j+1;
                k = k + ((j<l) ? d+1 : d);
            end
        end
    end
endfunction

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

genvar ga;
generate for (ga=0; ga<COE_NUM; ga=ga+1) begin:GA_ZERO
    a_coe_zero: assert property (@(posedge clk) disable iff (!rst_n)
        ((COE_ZERO_MAP[ga]==1'b0) || (in_coe[ga*CDW+:CDW]=={CDW{1'b0}}))
    ) else begin
        $error("in_coe[%0d:%0d] is not zero.", (ga*CDW+CDW-1), (ga*CDW));
        $stop;
    end
end endgenerate

// synopsys translate_on
`endif

endmodule
